package com.mtconnect.service;

import com.mtconnect.common.Constant;
import com.mtconnect.dao.ConditionMapper;
import com.mtconnect.dao.EventMapper;
import com.mtconnect.dao.SampleMapper;
import com.mtconnect.exception.DAOException;
import com.mtconnect.model.*;
import com.mtconnect.util.JDBCTemplete;
import com.mtconnect.util.ResourceLoad;
import com.mtconnect.util.StringUtil;
import com.mtconnect.util.XMLUtil;
import org.dom4j.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * Created by Administrator on 2017/9/11.
 * sample API 专用
 */
public class SampleService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SampleService.class);

    private JDBCTemplete jdbcTemplete;

    public SampleService(JDBCTemplete jdbcTemplete) {
        this.jdbcTemplete = jdbcTemplete;
    }

    /**
     * 无xpath路径，无指定设备
     *
     * @param sampleDTO 参数实体类
     * @return 返回完整的DeviceStream元素
     */
    public List<Element> nonePath(SampleDTO sampleDTO) {
        if (Objects.isNull(sampleDTO.getDeviceName())) {

            List<Node> dataItemElement = copy();
            //如果集合不存在则返回错误信息
            if (dataItemElement == null || dataItemElement.isEmpty()) {
                return null;
            }
            Map<Object, Object> elements = null;
            //创建dataItem对应的新元素
            if (sampleDTO instanceof SampleDTO) {
                elements = getNewElementBySample(sampleDTO.getFrom(), sampleDTO.getCount(), dataItemElement);
            } else {
                CurrentDTO c = (CurrentDTO) sampleDTO;
                elements = getNewElementByCurrent(c.getAt(), dataItemElement);
            }
            //创建DeviceStream元素
            List<Element> deviceStream = createDeviceStream(elements, dataItemElement);
            return deviceStream;
        } else {
            XPath xPath = DocumentHelper.createXPath("//d:Device[@name='" + sampleDTO.getDeviceName() + "']");
            xPath.setNamespaceURIs(Constant.DEVICE_XMLNS);
            Node node = null;
            for (Document document : ResourceLoad.DEVICES) {
                node = xPath.selectSingleNode(document);
                if (Objects.nonNull(node)) {
                    break;
                }
            }

            XPath dataItem = DocumentHelper.createXPath("//d:DataItem");
            dataItem.setNamespaceURIs(Constant.DEVICE_XMLNS);

            List<Node> list = dataItem.selectNodes(node);

            if (list.isEmpty()) {
                return null;
            }
            Map<Object, Object> elements = null;
            //创建dataItem对应的新元素
            if (sampleDTO instanceof SampleDTO) {
                elements = getNewElementBySample(sampleDTO.getFrom(), sampleDTO.getCount(), list);
            } else {
                CurrentDTO c = (CurrentDTO) sampleDTO;
                elements = getNewElementByCurrent(c.getAt(), list);
            }
            //创建DeviceStream元素
            List<Element> deviceStream = createDeviceStream(elements, list);
            return deviceStream;
        }
    }

    /**
     * 有xPath路径，无指定设备
     *
     * @param sampleDTO
     */
    public List<Element> selectByXPath(SampleDTO sampleDTO) {
        XPath xPath = DocumentHelper.createXPath(modifyXPath(sampleDTO.getxPath()));
        xPath.setNamespaceURIs(Constant.DEVICE_XMLNS);

        Document document = (Document) ResourceLoad.ALL_DEVICE.clone();
        List<Node> dataItem = new ArrayList<>();
        if (Objects.isNull(sampleDTO.getDeviceName())) {
            List<Node> list = xPath.selectNodes(document);
            if (Objects.isNull(list) || list.isEmpty()) {
                return null;
            }
            //--------------------找到当前xpath中所有的dataItem元素---------------------------
            getALLDataItem(document, list, dataItem);
            //------------------------------查找结束----------------------------------------
            if (dataItem.isEmpty()) {//如果没有任何结果就返回空值
                return null;
            }
            Map<Object, Object> elements = null;
            //创建dataItem对应的新元素
            if (sampleDTO instanceof SampleDTO) {
                elements = getNewElementBySample(sampleDTO.getFrom(), sampleDTO.getCount(), dataItem);
            } else {
                CurrentDTO c = (CurrentDTO) sampleDTO;
                elements = getNewElementByCurrent(c.getAt(), dataItem);
            }
            //创建DeviceStream元素
            List<Element> deviceStream = createDeviceStream(elements, dataItem);
            return deviceStream;
        } else {
            XPath xPath1 = document.createXPath("//d:Device[@name='" + sampleDTO.getDeviceName() + "']");
            xPath1.setNamespaceURIs(Constant.DEVICE_XMLNS);
            Node node = xPath1.selectSingleNode(document);
            try {
                Document device = DocumentHelper.parseText(node.asXML());
                List<Node> list = xPath.selectNodes(device);
                if (list.isEmpty()) {
                    return null;
                }
                getALLDataItem(device, list, dataItem);
                if (dataItem.isEmpty()) {//如果没有任何结果就返回空值
                    return null;
                }
                Map<Object, Object> elements = null;
                //创建dataItem对应的新元素
                if (sampleDTO instanceof SampleDTO) {
                    elements = getNewElementBySample(sampleDTO.getFrom(), sampleDTO.getCount(), dataItem);
                } else {
                    CurrentDTO c = (CurrentDTO) sampleDTO;
                    elements = getNewElementByCurrent(c.getAt(), dataItem);
                }
                //创建DeviceStream元素
                List<Element> deviceStream = createDeviceStream(elements, dataItem);
                return deviceStream;
            } catch (DocumentException e) {
                LOGGER.error("parse text as xml failed~!", e);
                LOGGER.error("text is {}", node.asXML());
            }
        }
        return null;
    }

    private void getALLDataItem(Document document, List<Node> list, List<Node> dataItem) {
        for (Node node : list) {
            if (node.getName().equals(Constant.DATA_ITEM)) {
                dataItem.add(node);
            } else if (node.getName().equals(Constant.REFERENCE)) {
                Element e = (Element) node;
                XPath dataItemId = DocumentHelper.createXPath("//d:DataItem[@id='" + e.attributeValue("dataItemId") + "']");
                dataItemId.setNamespaceURIs(Constant.DEVICE_XMLNS);
                dataItem.add(dataItemId.selectSingleNode(document));
            } else {
                XPath var1 = DocumentHelper.createXPath(".//d:DataItem");
                XPath var2 = DocumentHelper.createXPath(".//d:Reference");
                var1.setNamespaceURIs(Constant.DEVICE_XMLNS);
                var2.setNamespaceURIs(Constant.DEVICE_XMLNS);
                List<Node> list1 = var1.selectNodes(node);
                dataItem.addAll(list1);
                List<Node> list2 = var2.selectNodes(node);
                for (Node node1 : list2) {
                    Element e = (Element) node1;
                    XPath dataItemId = DocumentHelper.createXPath("//d:DataItem[@id='" + e.attributeValue("dataItemId") + "']");
                    dataItemId.setNamespaceURIs(Constant.DEVICE_XMLNS);
                    dataItem.add(dataItemId.selectSingleNode(document));
                }
            }
        }
    }

    /**
     * 创建DeviceStream元素
     *
     * @param elements dataItem对应的新的元素
     * @param dataItem 所有的dataItem
     * @return 完整的DeviceStream元素
     */
    private List<Element> createDeviceStream(Map<Object, Object> elements, List<Node> dataItem) {

        //存放所有的DeviceStream元素
        List<Element> allDevice = new ArrayList<>();

        //存放device以及其下的dataItem元素们
        Map<Object, List<Node>> device = new HashMap<>();


        for (Node node : dataItem) {
            Element temp = (Element) node;
            do {
                if (temp.getName().equals("Device")) {
                    if (device.containsKey(temp)) {
                        device.get(temp).add(node);
                    } else {
                        device.put(temp, new ArrayList<>());
                        device.get(temp).add(node);
                    }
                    break;
                }
                if (temp.isRootElement()) {
                    LOGGER.error("element is root {}", temp);
                    break;
                }
                temp = temp.getParent();
            } while (true);
        }
        //---------------------------------创建完整的DeviceStream元素
        for (Map.Entry<Object, List<Node>> entry : device.entrySet()) {
            Element deviceStream = DocumentHelper.createElement("DeviceStream");
            Element key = (Element) entry.getKey();
            deviceStream.addAttribute("name", key.attributeValue("name"));
            deviceStream.addAttribute("uuid", key.attributeValue("uuid"));
            //------------------------------对这个device下的dataItem进行分类，归类到对应的component下面
            List<Node> value = entry.getValue();
            Map<Object, List<Node>> classify = classify(elements, value);

            //--------------------------------创建所有的ComponentStream元素
            for (Map.Entry<Object, List<Node>> classifyEntry : classify.entrySet()) {
                List<Node> value1 = classifyEntry.getValue();
                List<Node> samples = new ArrayList<>();
                List<Node> event = new ArrayList<>();
                List<Node> condition = new ArrayList<>();
                Element newSamples = null;
                Element newEvents = null;
                Element newCondition = null;
                //----------------------对该component元素下的dataItem对应的新元素进行分类
                for (Node node : value1) {
                    if (node.getName().equals("Samples")) {
                        samples.add(node);
                    }
                    if (node.getName().equals("Events")) {
                        event.add(node);
                    }
                    if (node.getName().equals("Condition")) {
                        condition.add(node);
                    }
                }
                //----------------------------对重复的元素进行合并
                if (!samples.isEmpty()) {
                    newSamples = (Element) merge(samples);
                }
                if (!event.isEmpty()) {
                    newEvents = (Element) merge(event);
                }
                if (!condition.isEmpty()) {
                    newCondition = (Element) merge(condition);
                }

                //--------------------------------创建ComponentStream元素并添加属性
                Element stream = deviceStream.addElement("ComponentStream");
                Element key1 = (Element) classifyEntry.getKey();
                Map<String, String> attributes = new HashMap<>();
                if (null != key1.attributeValue("name")) {
                    attributes.put("name", key1.attributeValue("name"));
                }
                if (null != key1.attributeValue("nativeName")) {
                    attributes.put("nativeName", key1.attributeValue("nativeName"));
                }
                if (null != key1.attributeValue("uuid")) {
                    attributes.put("uuid", key1.attributeValue("uuid"));
                }
                if (null != key1.attributeValue("id")) {
                    attributes.put("componentId", key1.attributeValue("id"));
                }
                attributes.put("component", key1.getName());
                XMLUtil.additionalAttributes(stream, attributes);

                //----------------------添加子元素
                if (Objects.nonNull(newCondition)) {
                    stream.add(newCondition.createCopy());
                }
                if (Objects.nonNull(newEvents)) {
                    stream.add(newEvents.createCopy());
                }
                if (Objects.nonNull(newSamples)) {
                    stream.add(newSamples.createCopy());
                }
                //-------------------------------------------ComponentStream元素创建完毕
            }
            //-------------------------------------------DeviceStream元素创建完毕

            //添加到集合中
            allDevice.add(deviceStream);
        }
        return allDevice;
    }

    /**
     * 对device中的dataItem进行分类归类到具体的Components中
     * <p>
     * 将其放到一个map集合中，以ComponentStream对应的component元素为键，其下的dataItem子集为值
     *
     * @param elements dataItem对应的新元素的键值对集合
     * @param list     dataItem集合
     * @return map集合，包含ComponentStream对应的component元素，以及其下的dataItem子集
     */
    private Map<Object, List<Node>> classify(Map<Object, Object> elements, List<Node> list) {
        //键是component元素，值是这个元素下面所有的dataItem元素对应的新标签元素
        Map<Object, List<Node>> map = new HashMap<>();

        for (Node node : list) {
            Element parent = node.getParent().getParent();
            if (map.containsKey(parent)) {
                map.get(parent).add((Node) elements.get(node));
            } else {
                map.put(parent, new ArrayList<>());
                map.get(parent).add((Node) elements.get(node));
            }
        }
        return map;
    }

    /**
     * 将集合中元素的子元素进行合并，并且按照sequence进行升序排列
     * 例如：Samples元素可能会有多个，这个函数就是用来合并重复的Samples元素，并将它的child按照sequence属性进行升序排列。
     *
     * @param nodes 需要合并子集的集合
     * @return 合并完成的元素
     */
    private Node merge(List<Node> nodes) {
        String tagName = nodes.get(0).getName();
        List<Element> var = new ArrayList<>();

        for (Node node : nodes) {//将每个元素的子集放入新的集合中
            Element e = (Element) node;
            var.addAll(e.elements());
        }
        Collections.sort(var, (o1, o2) -> Long.compare(Long.parseLong(o1.attributeValue("sequence")), Long.parseLong(o2.attributeValue("sequence"))));
        Element element = DocumentHelper.createElement(tagName);
        for (Element element1 : var) {
            element.add(element1.createCopy());
        }
        return element;
    }

    /**
     * 创建需要返回的新的元素。
     *
     * @param from
     * @param count
     * @param dataItemElement dataItem元素集合。
     * @return 创建完成的新元素，以及对应的dataItem元素的键值对集合。新元素为值，dataItem为键。
     */
    private Map<Object, Object> getNewElementBySample(Long from, Long count, List<Node> dataItemElement) {
        //存放每个dataItem元素对应的新标签元素
        Map<Object, Object> elements = new HashMap<>();

        //遍历每个元素
        for (Iterator<Node> iterator = dataItemElement.iterator(); iterator.hasNext(); ) {
            Element e = (Element) iterator.next();
            String id = e.attributeValue("id");
            String type = e.attributeValue("type");
            String subtype = e.attributeValue("subtype");
            String category = e.attributeValue("category");
            String representation = e.attributeValue("representation");
            if (category.startsWith("C")) {
                String tableName = Constant.PRE_CONDITION + type;
                ConditionMapper conditionMapper = new ConditionMapper(this.jdbcTemplete);
                List<Condition> condition = null;
                try {
                    if (null == subtype) {
                        condition = conditionMapper.nonSubTypeBySample(tableName, id, from, count);
                    } else {
                        condition = conditionMapper.subTypeBySample(tableName, id, subtype, from, count);
                    }
                } catch (DAOException e1) {
                    LOGGER.error("query condition failed", e1);
                }
                if (condition.isEmpty()) {
                    iterator.remove();
                } else {
                    elements.put(e, XMLUtil.createConditionsElement(condition));
                }
            } else if (category.startsWith("S")) {
                String tableName = Constant.PRE_SAMPLE + type;
                SampleMapper sampleMapper = new SampleMapper(this.jdbcTemplete);
                List<Sample> samples = null;
                try {
                    if (null == subtype) {
                        samples = sampleMapper.nonSubTypeBySample(tableName, id, from, count);
                    } else {
                        samples = sampleMapper.subTypeBySample(tableName, id, subtype, from, count);
                    }
                } catch (DAOException e1) {
                    LOGGER.error("query sample failed", e1);
                }
                if (samples.isEmpty()) {
                    iterator.remove();
                } else {
                    String tag = null;
                    if (null != representation
                            && (representation.equals("TIME_SERIES") || representation.equals("DISCRETE"))) {
                        tag = StringUtil.underline2Camel(type + "_" + representation, false);
                    } else {
                        tag = type;
                    }
                    elements.put(e, XMLUtil.createSamplesElement(samples, tag));
                }
            } else if (category.startsWith("E")) {
                String tableName = Constant.PRE_EVENT + type;
                EventMapper eventMapper = new EventMapper(this.jdbcTemplete);
                List<Event> events = null;
                try {
                    if (null == subtype) {
                        events = eventMapper.nonSubTypeBySample(tableName, id, from, count);
                    } else {
                        events = eventMapper.subTypeBySample(tableName, id, subtype, from, count);
                    }
                } catch (DAOException e1) {
                    LOGGER.error("query event failed", e1);
                }
                if (events.isEmpty()) {
                    iterator.remove();
                } else {
                    String tag;
                    if (null != representation
                            && (representation.equals("TIME_SERIES") || representation.equals("DISCRETE"))) {
                        tag = StringUtil.underline2Camel(type + "_" + representation, false);
                    } else {
                        tag = type;
                    }
                    elements.put(e, XMLUtil.createEventElement(events, tag));
                }
            }
        }
        return elements;
    }

    /**
     * 创建需要返回的新的元素。
     *
     * @param at
     * @param dataItemElement dataItem元素集合。
     * @return 创建完成的新元素，以及对应的dataItem元素的键值对集合。新元素为值，dataItem为键。
     */
    private Map<Object, Object> getNewElementByCurrent(String at, List<Node> dataItemElement) {
        //存放每个dataItem元素对应的新标签元素
        Map<Object, Object> elements = new HashMap<>();

        //遍历每个元素
        for (Iterator<Node> iterator = dataItemElement.iterator(); iterator.hasNext(); ) {
            Element e = (Element) iterator.next();
            String id = e.attributeValue("id");
            String type = e.attributeValue("type");
            String subtype = e.attributeValue("subtype");
            String category = e.attributeValue("category");
            String representation = e.attributeValue("representation");
            if (category.startsWith("C")) {
                String tableName = Constant.PRE_CONDITION + type;
                ConditionMapper conditionMapper = new ConditionMapper(this.jdbcTemplete);
                Condition condition = null;
                try {
                    if (null == subtype) {
                        condition = conditionMapper.nonSubTypeByCurrent(tableName, id, at);
                    } else {
                        condition = conditionMapper.subTypeByCurrent(tableName, id, subtype, at);
                    }
                } catch (DAOException e1) {
                    LOGGER.error("query condition failed", e1);
                }
                if (Objects.isNull(condition)) {
                    iterator.remove();
                } else {
                    List<Condition> var = new ArrayList<>();
                    var.add(condition);
                    elements.put(e, XMLUtil.createConditionsElement(var));
                }
            } else if (category.startsWith("S")) {
                String tableName = Constant.PRE_SAMPLE + type;
                SampleMapper sampleMapper = new SampleMapper(this.jdbcTemplete);
                Sample sample = null;
                try {
                    if (null == subtype) {
                        sample = sampleMapper.nonSubTypeByCurrent(tableName, id, at);
                    } else {
                        sample = sampleMapper.subTypeByCurrent(tableName, id, subtype, at);
                    }
                } catch (DAOException e1) {
                    LOGGER.error("query sample failed", e1);
                }
                if (Objects.isNull(sample)) {
                    iterator.remove();
                } else {
                    String tag = null;
                    if (null != representation
                            && (representation.equals("TIME_SERIES") || representation.equals("DISCRETE"))) {
                        tag = StringUtil.underline2Camel(type + "_" + representation, false);
                    } else {
                        tag = type;
                    }
                    List<Sample> var = new ArrayList<>();
                    var.add(sample);
                    elements.put(e, XMLUtil.createSamplesElement(var, tag));
                }
            } else if (category.startsWith("E")) {
                String tableName = Constant.PRE_EVENT + type;
                EventMapper eventMapper = new EventMapper(this.jdbcTemplete);
                Event event = null;
                try {
                    if (null == subtype) {
                        event = eventMapper.nonSubTypeByCurrent(tableName, id, at);
                    } else {
                        event = eventMapper.subTypeByCurrent(tableName, id, subtype, at);
                    }
                } catch (DAOException e1) {
                    LOGGER.error("query event failed", e1);
                }
                if (Objects.isNull(event)) {
                    iterator.remove();
                } else {
                    String tag;
                    if (null != representation
                            && (representation.equals("TIME_SERIES") || representation.equals("DISCRETE"))) {
                        tag = StringUtil.underline2Camel(type + "_" + representation, false);
                    } else {
                        tag = type;
                    }
                    List<Event> var = new ArrayList<>();
                    var.add(event);
                    elements.put(e, XMLUtil.createEventElement(var, tag));
                }
            }
        }
        return elements;
    }

    /**
     * 复制dataItem元素的集合
     *
     * @return 返回一个复制完成的集合
     */
    private static List<Node> copy() {
        List<Node> var = new ArrayList<>();
        List<Node> sources = ResourceLoad.DATA_ITEM_ELEMENT;
        if (sources == null) {
            return var;
        }
        var.addAll(sources);
        return var;
    }

    /**
     * 对用户传入的xpath进行修改添加命名空间。
     *
     * @param XPath 用户传入的xpath
     * @return 返回添加命名空间后是xpath
     */
    private String modifyXPath(String XPath) {
        char[] chars = XPath.toCharArray();

        char previous = Character.MIN_VALUE;
        char next = Character.MIN_VALUE;
        Character of = Character.valueOf('/');

        //存储当前'/'在此字符串中的位置，从0开始下标
        List<Integer> list = new ArrayList<>();

        int length = chars.length;

        for (int i = 0,max = length-1; i < length; i++) {
            if (i == max) {
                next = Character.MIN_VALUE;
            } else {
                next = chars[i + 1];
            }

            //如果当前值大于0，那么此值赋值成前一个chars中的元素
            if (i > 0) {
                previous = chars[i - 1];
            }
            //如果当前元素是'/'符号，那么进行查看其前一个或者后一个是否也是相同元素。
            if (of.compareTo(chars[i]) == 0) {
                if (previous == '/' && previous != Character.MIN_VALUE) {
                    list.add(i);
                    continue;
                }
                if (next != '/' && next != Character.MIN_VALUE) {
                    list.add(i);
                }
            }
        }

        StringBuilder sb = new StringBuilder(XPath);
        for (int i = list.size()-1; i >=0 ; i--) {
            sb = sb.replace(list.get(i), list.get(i) + 1, "/d:");
        }
        return sb.toString();
    }

}
